#include "iic_peripheral.h"
#include "gd32f30x.h"
#include "stdio.h"

struct IICPeripheralManager {
	IICDelayMs delay;
};

struct IICPeripheralManager *sManager = NULL;

static void iic0Init(IIC_PERI_SPEED_E speed)
{
	rcu_periph_clock_enable(RCU_GPIOB);
	rcu_periph_clock_enable(RCU_I2C0);
	
	/* PB8->IIC0_SCL, PB9->IIC0_SDA */
	gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
	gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
	
	i2c_smbus_type_config(I2C0, I2C_SMBUS_HOST);
	i2c_clock_config(I2C0, speed, I2C_DTCY_2);
	i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, IIC0_SLAVE_ADDRESS7);
	i2c_enable(I2C0);
	i2c_ack_config(I2C0, I2C_ACK_ENABLE);
}

static void iic1Init(IIC_PERI_SPEED_E speed)
{
	rcu_periph_clock_enable(RCU_GPIOB);
	rcu_periph_clock_enable(RCU_I2C1);
	
	/* PB10->IIC1_SCL, PB11->IIC1_SDA */
	gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
	gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_11);
	
	i2c_clock_config(I2C0, speed, I2C_DTCY_2);
	i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, IIC1_SLAVE_ADDRESS7);
	i2c_enable(I2C1);
	i2c_ack_config(I2C0, I2C_ACK_ENABLE);
}

static void genStart(IIC_PERI_E iic)
{
	if (iic == IIC_PERIPHERAL0) {
		i2c_start_on_bus(I2C0);
	}
	else {
		i2c_start_on_bus(I2C1);
	}
}

static uint8_t waitStartDone(IIC_PERI_E iic, uint16_t timeout)
{
	IIC_ASSERT_POINT_NULL(sManager->delay);
	
	if (iic == IIC_PERIPHERAL0) {
		while (i2c_flag_get(I2C0, I2C_FLAG_SBSEND) == RESET) {
			timeout--;
			sManager->delay(1);
			if (timeout == 0) {
				return 1;
			}
		}
	}
	else {
		while (i2c_flag_get(I2C1, I2C_FLAG_SBSEND) == RESET) {
			timeout--;
			sManager->delay(1);
			if (timeout == 0) {
				return 1;
			}
		}
	}

	return 0;
}

static void sendSlaveAddrForWrite(IIC_PERI_E iic, uint32_t slave)
{
	if (iic == IIC_PERIPHERAL0) {
		i2c_master_addressing(I2C0, slave, I2C_TRANSMITTER);
	}
	else {
		i2c_master_addressing(I2C1, slave, I2C_TRANSMITTER);
	}
}

static void sendSlaveAddrForReceive(IIC_PERI_E iic, uint8_t slave)
{
	if (iic == IIC_PERIPHERAL0) {
		i2c_master_addressing(I2C0, slave, I2C_RECEIVER);
	}
	else {
		i2c_master_addressing(I2C1, slave, I2C_RECEIVER);
	}
}

static uint8_t waitAddrDone(IIC_PERI_E iic, uint16_t timeout)
{
	if (iic == IIC_PERIPHERAL0) {
		while (i2c_flag_get(I2C0, I2C_FLAG_ADDSEND) == RESET) {
			timeout--;
			sManager->delay(1);
			if (timeout == 0) {
				return 1;
			}
		}
		i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
	}
	else {
		while (i2c_flag_get(I2C1, I2C_FLAG_ADDSEND) == RESET) {
			timeout--;
			sManager->delay(1);
			if (timeout == 0) {
				return 1;
			}
		}
		i2c_flag_clear(I2C1, I2C_FLAG_ADDSEND);
	}

	return 0;
}

static uint8_t sendOneData(IIC_PERI_E iic, uint8_t data, uint16_t timeout)
{
	if (iic == IIC_PERIPHERAL0) {
		i2c_data_transmit(I2C0, data);
		while (i2c_flag_get(I2C0, I2C_FLAG_TBE) == RESET) {
			timeout--;
			sManager->delay(1);
			if (timeout == 0) {
				return 1;
			}
		}
	}
	else {
		i2c_data_transmit(I2C1, data);
		while (i2c_flag_get(I2C1, I2C_FLAG_TBE) == RESET) {
			timeout--;
			sManager->delay(1);
			if (timeout == 0) {
				return 1;
			}
		}
	}

	return 0;
}

static uint8_t receiveOneData(IIC_PERI_E iic, uint8_t *data, uint16_t timeout)
{
	if (iic == IIC_PERIPHERAL0) {
		while (i2c_flag_get(I2C0, I2C_FLAG_RBNE) == RESET) {
			timeout--;
			sManager->delay(1);
			if (timeout == 0) {
				return 1;
			}
		}
		*data = i2c_data_receive(I2C0);
	}
	else {
		while (i2c_flag_get(I2C1, I2C_FLAG_RBNE) == RESET) {
			timeout--;
			sManager->delay(1);
			if (timeout == 0) {
				return 1;
			}
		}
		*data = i2c_data_receive(I2C1);
	}

	return 0;
}

static void genStop(IIC_PERI_E iic)
{
	if (iic == IIC_PERIPHERAL0) {
		i2c_stop_on_bus(I2C0);
	}
	else {
		i2c_stop_on_bus(I2C1);
	}
}

static uint8_t waitStopDone(IIC_PERI_E iic, uint16_t timeout)
{
	if (iic == IIC_PERIPHERAL0) {
		while (I2C_CTL0(I2C0) & 0x0200) {
			timeout--;
			if (timeout == 0) {
				return 1;
			}
		}
	}
	else {
		while (I2C_CTL0(I2C1) & 0x0200) {
			timeout--;
			if (timeout == 0) {
				return 1;
			}
		}
	}

	return 0;
}

static void enableACK(IIC_PERI_E iic)
{
	if (iic == IIC_PERIPHERAL0) {
		i2c_ack_config(I2C0, I2C_ACK_ENABLE);
	}
	else {
		i2c_ack_config(I2C1, I2C_ACK_ENABLE);
	}
}

static void disableACK(IIC_PERI_E iic)
{
	if (iic == IIC_PERIPHERAL0) {
		i2c_ack_config(I2C0, I2C_ACK_DISABLE);
	}
	else {
		i2c_ack_config(I2C1, I2C_ACK_DISABLE);
	}
}

/**
 * @brief	IIC peripheral default init as master
 * @param	iic   IIC peripheral enum val
 * @param	speed IIC speed enum val
 * @param	delay A point to the actual delay implementation function
 */
uint8_t IICMasterDeInit(IIC_PERI_E iic, IIC_PERI_SPEED_E speed, IICDelayMs delay)
{
	static struct IICPeripheralManager manager;

	if (delay == NULL) {
		return 1;
	}
	
	if (sManager == NULL) {
		manager.delay = delay;
		
		sManager = &manager;
	}
	
	if (iic == IIC_PERIPHERAL0) {
		iic0Init(speed);
	}
	else {
		iic1Init(speed);
	}

	return 0;
}

/**
 * @brief	Set the delay function, a point to the actual implementation function
 * @param	delay The point to the function
 */
void IICSetDelay(IICDelayMs delay)
{
	if (delay != NULL) {
		sManager->delay = delay;
	}
}

/**
 * @brief	Send a data from special IIC peripheral
 * @param	iic       IIC peripheral enum val
 * @param	slaveAddr slave address
 * @param	data      The data to be sent
 * @param	timeout   timeout, unit: depend on the implementation of the delay set by the IICMasterDeInit() or IICSetDelay()
 * 					  1ms is recommended
 * @retval	0 - No error
 * 			1 - Gen start signal timeout
 * 			2 - Send slave address timeout
 * 			3 - Send byte timeout
 */
uint8_t IICMasterSendOneData(IIC_PERI_E iic, uint32_t slaveAddr, uint8_t data, uint16_t timeout)
{
	uint8_t result;
	
	genStart(iic);
	result = waitStartDone(iic, timeout);
	if (result) {
		return 1;
	}
	sendSlaveAddrForWrite(iic, slaveAddr);
	result = waitAddrDone(iic, timeout);
	if (result) {
		return 2;
	}
	result = sendOneData(iic, data, timeout);
	if (result) {
		return 3;
	}
	genStop(iic);
	return 0;
}

/**
 * @brief	Send array from special IIC peripheral
 * @param	iic       IIC peripheral enum val
 * @param	slaveAddr slave address
 * @param	array     The array to be sent
 * @param	len       THe length of the array
 * @param	timeout   timeout, unit: depend on the implementation of the delay set by the IICMasterDeInit() or IICSetDelay()
 * 					  1ms is recommended
 * @retval	0 - No error
 * 			1 - Gen start signal timeout
 * 			2 - Send slave address timeout
 * 			3 - Send a byte of the array timeout
 */
uint8_t IICMasterSendArray(IIC_PERI_E iic, uint32_t slaveAddr, uint8_t *array, uint16_t len, uint16_t timeout)
{
	uint8_t result;
	uint16_t i;

	IIC_ASSERT_POINT_NULL(array);

	genStart(iic);
	result = waitStartDone(iic, timeout);
	if (result) {
		return 1;
	}
	sendSlaveAddrForWrite(iic, slaveAddr);
	result = waitAddrDone(iic, timeout);
	if (result) {
		return 2;
	}
	for (i = 0; i < len; i++) {
		result = sendOneData(iic, array[i], timeout);
		if (result) {
			return 3;
		}
	}
	genStop(iic);

	return 0;
}

/**
 * @brief	Write a reg with IIC bus
 * @param	iic       IIC peripheral enum val
 * @param	slaveAddr slave address
 * @param	regAddr   REG address
 * @param	regVal    REG value
 * @param	timeout   timeout
 */
uint8_t IICMasterWriteOneReg(IIC_PERI_E iic, uint32_t slaveAddr, uint8_t regAddr, uint8_t regVal, uint16_t timeout)
{
	uint8_t result;
	
	genStart(iic);
	result = waitStartDone(iic, timeout);
	if (result) {
		return 1;
	}
	sendSlaveAddrForWrite(iic, slaveAddr);
	result = waitAddrDone(iic, timeout);
	if (result) {
		return 2;
	}
	result = sendOneData(iic, regVal, timeout);
	if (result) {
		return 3;
	}
	
	return 0;
}

/**
 * @brief	Receive a data from special IIC peripheral
 * @param	iic       IIC peripheral enum val
 * @param	slaveAddr slave address
 * @param	data      A point, return receive data
 * @param	timeout   timeout, unit: depend on the implementation of the delay set by the IICMasterDeInit() or IICSetDelay()
 * 					  1ms is recommended
 * @retval	0 - No error
 * 			1 - Gen start signal timeout
 * 			2 - Send slave address timeout
 * 			3 - Receive a byte timeout
 */
uint8_t IICMasterReceiveOneData(IIC_PERI_E iic, uint32_t slaveAddr, uint8_t *data, uint16_t timeout)
{
	uint8_t result;

	IIC_ASSERT_POINT_NULL(data);

	genStart(iic);
	result = waitStartDone(iic, timeout);
	if (result) {
		return 1;
	}
	sendSlaveAddrForReceive(iic, slaveAddr);
	result = waitAddrDone(iic, timeout);
	if (result) {
		return 2;
	}
	disableACK(iic);
	result = receiveOneData(iic, data, timeout);
	if (result) {
		return 3;
	}
	genStop(iic);
	waitStopDone(iic, timeout);
	enableACK(iic);

	return 0;
}

/**
 * @brief	Receive array from special IIC peripheral
 * @param	iic       IIC peripheral enum val
 * @param	slaveAddr slave address
 * @param	array     A point, return receive data
 * @param	len       Length of array
 * @param	timeout   timeout, unit: depend on the implementation of the delay set by the IICMasterDeInit() or IICSetDelay()
 * 					  1ms is recommended
 * @retval	0 - No error
 * 			1 - Gen start signal timeout
 * 			2 - Send slave address timeout
 * 			3 - Receive a byte timeout
 */
uint8_t IICMasterReceiveArray(IIC_PERI_E iic, uint32_t slaveAddr, uint8_t *array, uint16_t len, uint16_t timeout)
{
	uint8_t result;
	uint16_t i;
	uint16_t stop;

	IIC_ASSERT_POINT_NULL(array);

	genStart(iic);
	result = waitStartDone(iic, timeout);
	if (result) {
		return 1;
	}
	sendSlaveAddrForReceive(iic, slaveAddr);
	result = waitAddrDone(iic, timeout);
	if (result) {
		return 2;
	}
	stop = len - 1;
	for (i = 0; i < len; i++) {
		if (i == stop) {
			disableACK(iic);
		}
		result = receiveOneData(iic, &array[i], timeout);
		if (result) {
			return 3;
		}
	}
	genStop(iic);
	waitStopDone(iic, timeout);
	enableACK(iic);

	return 0;
}
